package org.dromara.common.tenant.core;

import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.core.dao.PlusSaTokenDao;

import java.time.Duration;
import java.util.List;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：SaToken 认证数据持久层 适配多租户
 */
/**
 * TenantSaTokenDao 类，继承自 PlusSaTokenDao
 * 该类提供了针对多租户应用的 SaToken 数据访问对象的实现
 * 主要用于处理不同租户的数据隔离和访问控制
 */
public class TenantSaTokenDao extends PlusSaTokenDao {

    /**
     * 重写get方法，用于从Redis中获取值
     * 此方法的目的是为特定的键前缀提供一个简化的访问方式
     * 它自动地将全局键前缀和给定的键组合起来，以避免每次获取或设置键时都重复拼接字符串
     *
     * @param key 要从Redis中获取的键的本地部分不包括全局前缀
     * @return 从Redis中获取的值，如果键不存在则可能返回null
     */
    @Override
    public String get(String key) {
        return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 设置指定键的值，并设置过期时间
     * 此方法用于在Redis中设置键值对，并为键添加一个过期时间
     * 在原有set方法的基础上，对键进行了前缀拼接，以统一管理键名
     *
     * @param key 要设置的键
     * @param value 要设置的值
     * @param timeout 过期时间，单位为秒如果timeout小于等于0，则该键不会过期
     */
    @Override
    public void set(String key, String value, long timeout) {
        super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
    }

    /**
     * 修修改指定key-value键值对 (过期时间不变)
     * 更新缓存中的键值对
     *
     * @param key 要更新的键
     * @param value 新的值
     * 此方法首先尝试获取与键关联的过期时间如果键不存在，则直接返回，不进行更新
     * 如果键存在，它将使用新的值和过期时间来更新键这确保了键的过期行为保持不变
     */
    @Override
    public void update(String key, String value) {
        // 获取与键关联的过期时间
        long expire = getTimeout(key);
        // -2 = 无此键
        if (expire == NOT_VALUE_EXPIRE) {
            return;
        }
        // 使用新的值和过期时间更新键
        this.set(key, value, expire);
    }

    /**
     * 删除Value
     * 重写删除缓存方法，以支持特定的键名前缀
     * 该方法主要用于删除特定前缀的Redis缓存数据
     *
     * @param key 缓存键名，将被拼接上前缀GLOBAL_REDIS_KEY后用于识别具体的缓存项
     */
    @Override
    public void delete(String key) {
        super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 获取Value的剩余存活时间 (单位: 秒)
     * 获取指定键的过期时间
     *
     * 此方法用于从Redis缓存中获取特定键的过期时间，单位为毫秒
     * 它通过在全局键前缀后附加给定的键来形成完整的Redis键名
     *
     * @param key 需要获取过期时间的键
     * @return 键的过期时间，如果键不存在或没有设置过期时间，则返回0
     */
    @Override
    public long getTimeout(String key) {
        return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 修改Value的剩余存活时间 (单位: 秒)
     * 更新指定键的超时时间
     * 如果timeout参数等于NEVER_EXPIRE常量，则尝试将键的超时时间设置为永久
     * 否则，将键的超时时间设置为指定的timeout值
     *
     * @param key 要更新超时时间的键
     * @param timeout 指定的超时时间，单位为秒如果为NEVER_EXPIRE，则表示设置为永久
     */
    @Override
    public void updateTimeout(String key, long timeout) {
        // 判断是否想要设置为永久
        if (timeout == NEVER_EXPIRE) {
            long expire = getTimeout(key);
            if (expire == NEVER_EXPIRE) {
                // 如果其已经被设置为永久，则不作任何处理
            } else {
                // 如果尚未被设置为永久，那么再次set一次
                this.set(key, this.get(key), timeout);
            }
            return;
        }
        // 使用Redis工具类设置键的超时时间
        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
    }


    /**
     * 获取Object，如无返空
     * 从Redis中获取与指定键关联的对象
     * 此方法通过在给定键前添加全局Redis键前缀来形成实际的键
     *
     * @param key 指定的对象键，不包含全局前缀
     * @return 与键关联的对象，如果找不到则返回null
     */
    @Override
    public Object getObject(String key) {
        // 调用父类的getObject方法，使用带有全局前缀的键
        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 写入Object，并设定存活时间 (单位: 秒)
     * 将对象设置到Redis中，并添加全局键前缀，以提高键的唯一性和区分度
     *
     * @param key 键，用于标识对象
     * @param object 要存储的对象
     * @param timeout 超时时间，单位为秒若为负值，则表示永不过期
     */
    @Override
    public void setObject(String key, Object object, long timeout) {
        super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
    }

    /**
     * 更新Object (过期时间不变)
     * @param key 键，用于标识缓存中的对象
     * @param object 要更新的对象
     *
     * 此方法首先检查指定键的对象是否存在以及其过期时间，如果对象存在则更新该对象及其过期时间，
     * 如果不存在则不进行任何操作。这种设计允许在不改变原有对象的情况下，通过键更新缓存中的对象，
     * 同时保持对象的过期策略不变。
     */
    @Override
    public void updateObject(String key, Object object) {
        // 获取指定键的对象的过期时间
        long expire = getObjectTimeout(key);
        // -2 = 无此键
        if (expire == NOT_VALUE_EXPIRE) {
            // 如果键不存在，直接返回，不执行更新操作
            return;
        }
        // 更新对象及其过期时间
        this.setObject(key, object, expire);
    }

    /**
     * 删除Object
     * 删除指定的缓存对象
     * 该方法覆盖了父类的deleteObject方法，目的是为了在删除缓存时自动拼接一个全局前缀
     * 这样做可以避免直接操作缓存键名，从而在一定程度上实现键名的统一管理和维护
     *
     * @param key 需要删除的缓存对象的键名（不包含全局前缀）
     */
    @Override
    public void deleteObject(String key) {
        super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 获取Object的剩余存活时间 (单位: 秒)
     * 获取对象的超时时间
     *
     * 此方法用于获取指定键的对象在Redis中的超时时间
     * 它通过在给定键前加上全局前缀来形成最终的键，
     * 然后调用父类的getObjectTimeout方法查询超时时间
     *
     * @param key 键，用于获取对象超时时间，将被添加到全局前缀后形成最终查询的键
     * @return 返回对象的超时时间，单位为毫秒如果对象没有设置超时，则返回-1
     */
    @Override
    public long getObjectTimeout(String key) {
        return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
    }

    /**
     * 修改Object的剩余存活时间 (单位: 秒)
     * 更新对象的超时时间
     *
     * @param key 对象的键
     * @param timeout 对象的新超时时间，单位为秒如果timeout等于NEVER_EXPIRE，表示将对象设置为永不过期
     */
    @Override
    public void updateObjectTimeout(String key, long timeout) {
        // 判断是否想要设置为永久
        if (timeout == NEVER_EXPIRE) {
            long expire = getObjectTimeout(key);
            if (expire == NEVER_EXPIRE) {
                // 如果其已经被设置为永久，则不作任何处理
            } else {
                // 如果尚未被设置为永久，那么再次set一次
                this.setObject(key, this.getObject(key), timeout);
            }
            return;
        }
        // 使用RedisUtils设置对象的超时时间
        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
    }


    /**
     * 搜索数据
     * 根据给定的前缀和关键字在Redis中搜索数据
     * 此方法通过组合全局Redis键和提供的前缀来查询，实现了对Redis数据搜索的功能
     *
     * @param prefix    搜索关键字的前缀，用于限定搜索范围
     * @param keyword   搜索的关键字，用于匹配数据
     * @param start     搜索结果的起始位置，用于分页
     * @param size      搜索结果的返回数量，用于分页
     * @param sortType  是否按照排序类型返回结果，true为按排序返回
     * @return          返回匹配的字符串列表
     */
    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
        // 构建全局Redis键，用于搜索数据
        return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
    }
}
